package com.ksyun.campus.metaserver.services;

import com.google.gson.Gson;
import com.ksyun.campus.metaserver.domain.FileType;
import com.ksyun.campus.metaserver.domain.ReplicaData;
import com.ksyun.campus.metaserver.domain.StatInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.utils.ZKPaths;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

@Service
@Slf4j
public class FsckServices {

    @Autowired
    private CuratorFramework curatorFramework;
    
    @Autowired
    private Gson gson;

    //@Scheduled(cron = "0 0 0 * * ?") // 每天 0 点执行
    @Scheduled(fixedRate = 30*60*1000) // 每隔 30 分钟执行一次
    public void fsckTask() {

        try {
            String znodePath="/dataStatusBackups";
            // 1、全量扫描文件列表
            List<String> fileList=new ArrayList<>();
            traverseZKFiles(curatorFramework,znodePath,fileList);
            // 存储已经找到的副本节点
            List<String> foundReplicas = new ArrayList<>();
            // 存储未找到的副本结点
            List<String> notFoundReplicas=new ArrayList<>();
            for (String file :fileList) {
                byte[] bytes = curatorFramework.getData().forPath(file);
                String statsJson = new String(bytes);
                StatInfo statInfo = gson.fromJson(statsJson, StatInfo.class);
                List<ReplicaData> replicaDataList = statInfo.getReplicaData();
                //从文件中拿到该文件对应存储到的副本结点信息
                for (ReplicaData replicaData :replicaDataList) {
                    String dsNode = replicaData.getDsNode();
                    // 2、检查文件副本数量是否正常
                    // 查看副本结点中有无这个文件存在
                    String fileExist = file.replace("/dataStatusBackups", "/fileList/" + dsNode);
                    log.info("存储该文本文件的副本结点应该为:{}",fileExist);
                    Stat stat = curatorFramework.checkExists().forPath(fileExist);
                    // 如果文件结点不存在，说明需要去从存在这个文件结点的副本中找到
                    if(stat==null){
                        // 将这个结点标记一下
                        notFoundReplicas.add(dsNode);
                    } else {
                        foundReplicas.add(dsNode);
                    }
                }
                // 同步恢复副本结点
                // 3、更新文件副本数：3副本
                if(!notFoundReplicas.isEmpty()){//如果有文件需要恢复
                    //只考虑了至少有一个副本结点存有文件结点的情况
                    if(foundReplicas.isEmpty()){
                        log.info("没有任何副本存储了该节点的信息");
                    }
                    String foundDSNode = foundReplicas.get(0);
                    //得到找到了文件的结点总路径
                    String fileFound = file.replace("/dataStatusBackups", "/fileList/" + foundDSNode);

                    byte[] replicaData = curatorFramework.getData().forPath(fileFound);

                    for (int i=0;i<notFoundReplicas.size();i++) {
                        String notFoundNode =notFoundReplicas.get(i);
                        String fileNotFoundStr = file.replace("/dataStatusBackups", "/fileList/" + notFoundNode);
                        curatorFramework.create().creatingParentsIfNeeded().forPath(fileNotFoundStr,replicaData);
                        File notFoundFile = new File(fileNotFoundStr);
                        String fileName = notFoundFile.getName();
                        // 4、 记录当前检查结果
                        log.info("----{} 副本的 {} 文件已恢复------",notFoundNode,fileName);
                        //清理掉这个已经恢复的结点
                        notFoundReplicas.remove(i);
                    }
                    //清理掉找到了文件数据的结点,避免影响下一个恢复周期
                    foundReplicas.clear();
                }else {
                    //清理掉找到了文件数据的结点,避免影响下一个恢复周期
                    foundReplicas.clear();
                    log.info("------全部文件正常，无需恢复文件-------");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    //递归遍历出所有文件
    public void traverseZKFiles(CuratorFramework curatorFramework, String znodePath,List<String> fileList) throws Exception {
        Stat stat = curatorFramework.checkExists().forPath(znodePath);
        if (stat != null) {
            // 遍历子节点
            for (String child : curatorFramework.getChildren().forPath(znodePath)) {
                // 构建子节点路径
                // 使用ZKPaths工具类构建子节点的完整路径。
                String childPath = ZKPaths.makePath(znodePath, child);
                // 获取子节点的元数据信息
                Stat childStat = curatorFramework.checkExists().forPath(childPath);
                if (childStat != null) {
                    byte[] bytes = curatorFramework.getData().forPath(childPath);
                    String statsJson = new String(bytes);
                    StatInfo statInfo = gson.fromJson(statsJson, StatInfo.class);
                    // 判断节点类型是否为文件
                    if (statInfo != null && statInfo.getType().equals(FileType.File)) {
                        fileList.add(childPath);
                        log.info("文件结点为:{}",childPath);
                    }else {
                        // 如果子节点有子节点，递归遍历子节点的子节点
                        traverseZKFiles(curatorFramework, childPath,fileList);
                    }
                }
            }
        } else {
            log.info("ZNode does not exist.");
        }
    }
}
